#include "sched_lottery.h"
#include <iostream>
#include <stdlib.h>
#include <fstream> // PARA ANALISIS

using namespace std;

const int NEXT_TASK = -23;
ofstream logGlobal;
ofstream datosFinales;
	
typedef pair<int, int> taskWithTicket; 

SchedLottery::SchedLottery(vector<int> argn) {
	int cores = argn[0];
	quantum = argn[1];
	int seed = argn[2];
	srand(seed);

	logGlobal.open ("/home/jlopez/tp-so-2do-2013/TP1/ej10/logCompleto.txt", ios::out | ios::app);
	datosFinales.open ("TicksXTarea.txt", ios::out | ios::app);

	cout << "Number of cores: " << cores << "\nQuantum for each core: " << quantum << "\nSeed: " << seed << endl;

	for(int i = 0; i < cores; i++){
		quantum_left.push_back(quantum);
		current_tick_by_cpu.push_back(0);
	}

	tickets_distributed = 0;	
	base_tickets = 100;
	lotteryNumber = 0; //PARA ANALSIS
}

SchedLottery::~SchedLottery() {
	for(int i = 0; i < total_tick_by_cpu.size(); i++){
		datosFinales << "TAREA " << i << ": " << total_tick_by_cpu[i] << endl;
	}

	logGlobal.close();
	datosFinales.close();
}

// PARA ANALISIS

void SchedLottery::write(char* filename, struct lottery_information lottery_info, bool logCompleto){
	if(logCompleto){
		logGlobal << lottery_info.toString();
	} else {
		datosFinales << lottery_info.toStringEstadistica();
	}
}
// PARA DEBUG
void show(map<int, int> m){
	map<int, int>::iterator it;
	for(it = m.begin(); it != m.end(); it++) {
		cout << "<" << it->first << "," << it->second << "> |";
	}
}

int SchedLottery::checkWinner(int winning_ticket){
	// This function iterates over the activeTasks and decides who is the winner
	cout << "Winning ticket is " << winning_ticket << ". Total amount of tickets distributed is " << tickets_distributed << endl;
	cout << "Checking winner between these active tasks: ";
	show(activeTasks);
	cout << "\n";
	map<int, int>::iterator task;
	int sum = 0;
	int winner = -2;
	for(task = activeTasks.begin(); task != activeTasks.end(); task++) {
		sum = sum + task->second;

		if(sum >= winning_ticket){
			
			winner = task->first;
			
			cout << "Winner task: " << winner << endl;

			break;
		}
	}
	return winner;
}

void SchedLottery::load(int pid) {
	cout << "Loading task " << pid << "...";
	activeTasks.insert(std::pair<int,int>(pid, base_tickets));
	tickets_distributed += base_tickets;

	total_tick_by_cpu.push_back(0);

	struct lottery_information lottery_info; // PARA ANALISIS
	lottery_info.cpu = -2; // PARA ANALISIS
	lottery_info.tick = -2; // PARA ANALISIS
	lottery_info.winner = -2; // PARA ANALISIS
	lottery_info.numberOfVictories = 0; //PARA ANALISIS
	lottery_info.numberOfLotteries = 0; //PARA ANALISIS
	infoTasks.push_back(lottery_info);  //PARA ANALISIS

	cout << "DONE!" << endl;
}

void SchedLottery::unblock(int pid) {
	cout << "Unblocking task " << pid << "...";
	map<int,int>::iterator taskToUnblock;

	// First we find the task we want to unblock in the blockedTasks map.
	taskToUnblock = blockedTasks.find(pid);
	
	if(taskToUnblock != blockedTasks.end()){
		// If the taskToUnblock is in the map

		// We insert the taskToUnblock in the activeTasks map.
		activeTasks.insert(std::pair<int,int>(taskToUnblock->first, taskToUnblock->second));

		// When a task is blocked we substract its tickets.
		// When a task is unblocked we have to restore its tickets (including the compensation)
		tickets_distributed += taskToUnblock->second; 

		// Then we erase the taskToUnblock from the blockedTasks map.
		blockedTasks.erase(taskToUnblock);

	}

	cout << "DONE!" << endl;
}

void SchedLottery::switchTaskTick(int running_pid, int nextPid){

	if(running_pid != IDLE_TASK){
		activeTasks.insert(std::pair<int,int>(running_pid, base_tickets));
		tickets_distributed += base_tickets;
	}

	map<int,int>::iterator taskToRun;
	taskToRun = activeTasks.find(nextPid);
	if(taskToRun != activeTasks.end()){
		tickets_distributed -= taskToRun->second;
		activeTasks.erase(taskToRun);
	}

}

void SchedLottery::switchTask(int nextPid){
	map<int,int>::iterator taskToRun;
	taskToRun = activeTasks.find(nextPid);
	if(taskToRun != activeTasks.end()){
		tickets_distributed -= taskToRun->second;
		activeTasks.erase(taskToRun);
	}
}

int SchedLottery::next(int cpu, Motivo m){
	int nextTask = IDLE_TASK;
	if(!activeTasks.empty()){

		quantum_left[cpu] = quantum;

		// We get a random number from 0 to 'tickets_distributed' to see which task is going to win the lottery.
		
		cout << "Obtaining a winning ticket between 0 and " << tickets_distributed << ". "; 

		int winning_ticket = rand() % tickets_distributed;	
		int lottery_winner = checkWinner(winning_ticket);
		
		lotteryNumber++; //PARA ANALISIS
		
		if(m == TICK){
			switchTaskTick(current_pid(cpu), lottery_winner);
			nextTask = lottery_winner;	
		} else if(m == EXIT || m == BLOCK) {
			switchTask(lottery_winner);
			nextTask = lottery_winner;
		}
			
		struct lottery_information lottery_info; // PARA ANALISIS
	    lottery_info.cpu = cpu; // PARA ANALISIS
	    lottery_info.tick = current_tick_by_cpu[cpu]; // PARA ANALISIS
	    lottery_info.winner = nextTask; // PARA ANALISIS
	    lottery_info.numberOfVictories = infoTasks[nextTask].numberOfVictories + 1;
	    lottery_info.numberOfLotteries = lotteryNumber;
	    infoTasks[nextTask] = lottery_info;
	    write("logCompleto.txt", lottery_info, true); //PARA ANALISIS
	} else if(activeTasks.empty() && m == TICK && current_pid(cpu) != IDLE_TASK) {
		quantum_left[cpu] = quantum;
		activeTasks.insert(std::pair<int,int>(current_pid(cpu), base_tickets));
		tickets_distributed += base_tickets;
	}
	return nextTask;
}

void SchedLottery::resetTicket(map<int, int> * m, int key){

	map<int, int>::iterator taskToModify;

	// We find the task we want to modify
	taskToModify = m->find(key);
	if(taskToModify != m->end()){
		// If the task is in the map then we modify its ticket.
		int compensation = taskToModify->second - base_tickets;
		compensation = 0;
		taskToModify->second = base_tickets;

		// We reduce the 'tickets_distributed' counter
		tickets_distributed -= compensation;

		cout << "Reset ticket for Task " << key << " - Modifying ticket to " << base_tickets << ".\nTotal tickets distributed: " << tickets_distributed << "\nEnd reset ticket.";
	}
}

int SchedLottery::handleTick(int cpu){
	cout << "Reason: TASK " << current_pid(cpu) << " IN CPU " << cpu <<" TICK" << endl;

	if(quantum_left[cpu] == 0 || current_pid(cpu) == IDLE_TASK){
		return NEXT_TASK;
	} else if (quantum == quantum_left[cpu] - 1){
		cout << "First tick for task " << current_pid(cpu) << "\n Calling resetTicket for activeTasks map." << endl;
		resetTicket(&activeTasks, current_pid(cpu));
	}

	return current_pid(cpu);
}

void SchedLottery::handleBlock(int cpu){
	cout << "Reason: TASK " << current_pid(cpu) << " IN CPU " << cpu <<" BLOCK" << endl;

	// next(cpu) erases the running task (current_pid(cpu)) from activeTasks. We just have to insert this task with its new ticket in the blockedTasks map.
	int compensation;
	
	compensation = base_tickets* (quantum / (quantum - quantum_left[cpu]));
	compensation = 0;
 	blockedTasks.insert(std::pair<int,int>(current_pid(cpu), base_tickets + compensation));

 	// Como lo metimos en las bloqueadas aca no se hace ninguna cuenta sobre la cantidad total de tickets.
}

void SchedLottery::handleExit(int cpu){
	cout << "Reason: TASK " << current_pid(cpu) << " IN CPU " << cpu <<" EXIT" << endl;
}

int SchedLottery::tick(int cpu, const enum Motivo m) {
	cout << "TICK!\nActive tasks: ";
	show(activeTasks);
	cout << "\nBlocked taks: ";
	show(blockedTasks);
	cout << "\n";
	int tarea = current_pid(cpu);
	if(tarea != -1){
		total_tick_by_cpu[tarea]++;
	}
	current_tick_by_cpu[cpu]++;
	quantum_left[cpu]--;

	switch (m)
	{
		case TICK:
		{
			int result = handleTick(cpu);
			if(result != NEXT_TASK){
				return result;
			} else {
				return next(cpu, m);
			}

		}
		case BLOCK:
		{
			handleBlock(cpu);
		    break;
		}
		case EXIT:
		{
			handleExit(cpu);
		    break;
		}
	}
	

	return next(cpu, m);
}
